----------------------------------------------------------------------------------
--- Contains components for 'Execute' portion of pipeline 
--- AND
--- the EX/MEM register
----------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity EXECUTE_UNIT is
	PORT (
	
	CLK : in STD_LOGIC;
	
	----------- CONTROL SIGNALS-----------
		-- USED CONTROL SIGNALS
	REG_DEST_IN : in STD_LOGIC; -- Which of the 5-bits will represent the destination register? If 0, then 20 downto 16. If 1, then 25 downto 21
	ALU_SRC_IN : in STD_LOGIC; -- WHAT should OP2 hold? 1 for immedate/offset, and 0 REGFILE2_IN
	ALU_OP_IN : in STD_LOGIC_VECTOR(5 downto 0); -- operation for ALU OP
		-- "FLOW-THROUGH" control signals (just passed onto next layer without any processing)
	MEM_READ_IN : in STD_LOGIC;
	MEM_WRITE_IN : in STD_LOGIC;
	REG_WRITE_IN : in STD_LOGIC;
	MEM_TO_REG_IN : in STD_LOGIC;
	
		-- DATA SIGNALS
	CUR_PC_IN : in STD_LOGIC_VECTOR(31 downto 0); -- PC to be added in
	IMMEDIATE_IN : in STD_LOGIC_VECTOR(31 downto 0); -- Immediate or offset
	REGFILE1_IN : in STD_LOGIC_VECTOR(31 downto 0); -- output from register file
	REGFILE2_IN : in STD_LOGIC_VECTOR(31 downto 0); -- output from register file
	REGDEST1_IN : in STD_LOGIC_VECTOR(4 downto 0); -- Register destination 20 downto 16
	REGDEST2_IN : in STD_LOGIC_VECTOR(4 downto 0); -- Register destination 25 downto 21
	
	-- SIGNALS to handle data hazards
		-- To know what signals are provided by the register file, for comparison later on
	REGSRC1_IN : in STD_LOGIC_VECTOR(4 downto 0); -- for RS
	REGSRC2_IN : in STD_LOGIC_VECTOR(4 downto 0); -- for RT
	
		-- To know what earlier instructions modified, so we can take necessary steps to fix them
	EX_MEM_REG_DEST : in STD_LOGIC_VECTOR(4 downto 0); -- Destination address of an earlier instruction
	EX_MEM_REG_VAL : in STD_LOGIC_VECTOR(31 downto 0); -- Actual value of ALU, later down in the pipeline (earlier instruction in time)
	EX_MEM_REG_IS_WRITE : in STD_LOGIC; -- Is an earlier instruction (which has just completed the EX combinatorial stage) writing to registers?
	MEM_WB_REG_DEST : in STD_LOGIC_VECTOR(4 downto 0); 
	MEM_WB_REG_VAL : in STD_LOGIC_VECTOR(31 downto 0);
	MEM_WB_REG_IS_WRITE : in STD_LOGIC; -- Is an earlier instruction (which has just completed the MEM combinatorial stage) writing to registers?
		
	----------- OUTPUTS ----------- 
	MEM_READ_OUT : out STD_LOGIC;
	MEM_WRITE_OUT : out STD_LOGIC;
	MEM_TO_REG_OUT : out STD_LOGIC; -- Is this a load word operation? 
	REG_WRITE_OUT : out STD_LOGIC;
	
	ALU_OUT : out STD_LOGIC_VECTOR(31 downto 0); -- Output from the ALU
	WRITE_DATA_OUT : out STD_LOGIC_VECTOR(31 downto 0); -- immediate or register value to be written to word
	REG_DEST_OUT : out STD_LOGIC_VECTOR(4 downto 0); -- Destination register for later phases
	
	-- For MEM_MEM data hazards
	STORE_SOURCE : out STD_LOGIC_VECTOR(4 downto 0); -- RT for a LW operation
	
	-- For branch hazards [COMBINATORIAL OUTPUT!!!!!!!]
	REG_WRITEBACK_DEST : out STD_LOGIC_VECTOR(4 downto 0)
	
	);
end EXECUTE_UNIT;

architecture Behavioral of EXECUTE_UNIT is

	Component alu is
	Port (	
		Clk			: in	STD_LOGIC;
		Control		: in	STD_LOGIC_VECTOR (5 downto 0);
		Operand1		: in	STD_LOGIC_VECTOR (31 downto 0);
		Operand2		: in	STD_LOGIC_VECTOR (31 downto 0);
		Result1		: out	STD_LOGIC_VECTOR (31 downto 0);
		Result2		: out	STD_LOGIC_VECTOR (31 downto 0);
		Debug			: out	STD_LOGIC_VECTOR (31 downto 0));
	end Component;

	signal alu_op1 : STD_LOGIC_VECTOR(31 downto 0);
	signal alu_op2 : STD_LOGIC_VECTOR(31 downto 0);
	signal alu_internal_out : STD_LOGIC_VECTOR(31 downto 0);
	signal alu_debug : STD_LOGIC_VECTOR(31 downto 0);

	signal writeback_reg_dest : STD_LOGIC_VECTOR(4 downto 0);
	
	-- Signal containing the real value of register 2 (RT), after taking into account hazards
	signal reg2_real : STD_LOGIC_VECTOR(31 downto 0);

begin
	
	-- Instantiate ALU
	main_alu : alu 
	PORT MAP (	
		Clk			=> CLK,
		Control		=> ALU_OP_IN,
		Operand1		=> alu_op1,
		Operand2		=> alu_op2,
		Result1		=> alu_internal_out,
		--- Result 2 and Debug not used for now
		Result2		=> OPEN,
		Debug => alu_debug

	);

   -- Select destination register address, based on REG_DEST_IN control
	writeback_reg_dest <= REGDEST1_IN when REG_DEST_IN = '0' else REGDEST2_IN;
	REG_WRITEBACK_DEST <= writeback_reg_dest; 

	-- Select correct input signals to the ALU, while taking care of data hazards.
	process(REGSRC1_IN, 
			  REGSRC2_IN, 
			  EX_MEM_REG_DEST, 
			  EX_MEM_REG_VAL, 
			  MEM_WB_REG_DEST, 
			  MEM_WB_REG_VAL, 
			  EX_MEM_REG_IS_WRITE,
			  MEM_WB_REG_IS_WRITE,
			  IMMEDIATE_IN,
			  ALU_SRC_IN,
			  writeback_reg_dest,
			  REGFILE1_IN,
			  REGFILE2_IN
			  )
		-- To resolve "priority" conflicts in forwarding. We always forward from the unit nearest
		-- to EXEC (ie. EXEC/MEM followed by MEM/WB), since those instructions are newer and already
		-- contain the latest values, if they themselves require forwarding.
		
		--- "Real" values of registers, taking into account data hazards
		variable R1_REAL : STD_LOGIC_VECTOR(31 downto 0);
		variable R2_REAL : STD_LOGIC_VECTOR(31 downto 0);
	begin
	
		-- Fix RS's (ALU_OP1) value
		if (REGSRC1_IN /= "00000" -- 0 register is a constant, so we should not have forwarding here (actually this is redundant)
		and EX_MEM_REG_IS_WRITE = '1' -- the previous instruction involves some writing to "SOME" register
		and EX_MEM_REG_DEST = REGSRC1_IN -- And that previous instruction involves writing to the SAME register we are using now
		) then
			-- Forward from EX_MEM unit
			R1_REAL := EX_MEM_REG_VAL; -- Use this as the first operand for the ALU
		elsif (REGSRC1_IN /= "00000"
		and MEM_WB_REG_IS_WRITE = '1'
		and MEM_WB_REG_DEST = REGSRC1_IN
		) then
			-- Forward from MEM_WB unit
			R1_REAL := MEM_WB_REG_VAL;
		else
			-- No need to forward, use the "normal" output from the register file
			R1_REAL := REGFILE1_IN; 
		end if;
		alu_op1 <= R1_REAL; -- ALU always reads in from the first register
		
		-- Same as the above, but fix RT's value
		if (REGSRC2_IN /= "00000" -- 0 register is a constant, so we should not have forwarding here (actually this is redundant)
		and EX_MEM_REG_IS_WRITE = '1' -- the previous instruction involves some writing to "SOME" register
		and EX_MEM_REG_DEST = REGSRC2_IN -- And that previous instruction involves writing to the SAME register we are using now
		) then
			-- Forward from EX_MEM unit
			R2_REAL := EX_MEM_REG_VAL; -- Use this as the first operand for the ALU
		elsif (REGSRC2_IN /= "00000"
		and MEM_WB_REG_IS_WRITE = '1'
		and MEM_WB_REG_DEST = REGSRC2_IN
		) then
			-- Forward from MEM_WB unit
			R2_REAL := MEM_WB_REG_VAL;
		else
			-- No need to forward, use the "normal" output from the register file
			R2_REAL := REGFILE2_IN; 
		end if;
		Reg2_real <= R2_REAL; -- extract this signal out from this combi process, to be used in clocked process
		
		if (ALU_SRC_IN = '1') then -- If operand2's source is immediate, then obviously forwarding is pointless
			alu_op2 <= IMMEDIATE_IN;
		else
			alu_op2 <= R2_REAL;
		end if;
		
	end process;

	-- Can use behavioral model here, just link up components and fill up the latch accordingly
	process(CLK)
	begin
	
		if (CLK'event and CLK = '1') then
			-- FLOW THROUGH OUTPUTS/CTRL SIGNALS
			MEM_READ_OUT <= MEM_READ_IN;
			MEM_WRITE_OUT <= MEM_WRITE_IN;
			REG_WRITE_OUT <= REG_WRITE_IN;
			MEM_TO_REG_OUT <= MEM_TO_REG_IN;
									
			--- Compute write data
			WRITE_DATA_OUT <= Reg2_real;
			
			--- Copy RT into store source, used for mem_mem data hazards
			STORE_SOURCE <= REGSRC2_IN;
			
			-------- Simple Operations --------
			REG_DEST_OUT <= writeback_reg_dest;
		
			--- Save ALU_OUT into the register
			ALU_OUT <= alu_internal_out;
		end if;
	end process;
end Behavioral;

